AArch64 (ARM64) のスタック操作
AArch64(ARM64)でスタックを操作しようとするもエラーが出まくるので、以下を参考にAArch64のスタック操作の基本を練習した。
サンプルコード
以下はHello Worldを出力するコード。これをベースに色々と手を入れる。実行するにはXcodeに雑にソースを追加しCommand-Rキーを押す。
code:hello.S
.global _main
.align 2
_main:
// print Hello World!
adr x1, helloworld // string to print
mov X2, #13 // length of our string mov X16, #4 // Unix write system call svc #0x80 // Call kernel to output the string // exit
mov x0, #0 // Use 0 return code mov x16, #1 // System call number 1 terminates this program svc #0x80 // Call kernel to terminate the program helloworld: .ascii "Hello World!\n"
実行結果はこんな感じ
https://gyazo.com/497b23be9a6aa44b395e849f64707592
ブレークポイントで止める
アセンブリソースにもブレークポイントを設定できる
https://gyazo.com/22e2734c374d5262e5d66e15a09a1ab0
レジスタに値をセット
code:diff
.global _main
.align 2
_main:
+ // レジスタに値をセット
+ mov x0, 0x00
+ mov x1, 0x10
+ mov x2, 0x30
+ mov x3, 0x40
+
https://gyazo.com/81556ff7f83aa7590d954f08db80b97f
スタックへのPUSHとPOP
code:diff
.global _main
.align 2
_main:
+ // レジスタに値をセット
+ mov x0, 0x00
+ mov x1, 0x10
+ mov x2, 0x20
+ mov x3, 0x30
+
+
+ ldr x0, sp, #16 // pop {x3} + ldr x1, sp, #16 // pop {x2} + ldr x2, sp, #16 // pop {x1} + ldr x3, sp, #16 // pop {x0} +
レジスタへ設定した値をスタックへPUSH
code:diff
+
https://gyazo.com/a26402b3e11e1a8bc01f2a673f7c0f5d
スタックからPOPした値をレジスタへ復帰
code:diff
+ ldr x0, sp, #16 // pop {x3} + ldr x1, sp, #16 // pop {x2} + ldr x2, sp, #16 // pop {x1} + ldr x3, sp, #16 // pop {x0} https://gyazo.com/651a2e9df4959ae666fb7fbc803fcf64
スタック操作とSPレジスタの値
スタックへのPUSH(str x0, [sp, #-16]!)を行うと SP レジスタの値が自動で減少し、スタックからのPOPを行うと SP レジスタの値が自動で増加するみたい
code:asm
// スタック操作前: sp = 0x000000016fdff430
str x0, sp, #-16! // sp = 0x000000016fdff420 (=> 16減少) str x1, sp, #-16! // sp = 0x000000016fdff410 (=> 16減少) str x2, sp, #-16! // sp = 0x000000016fdff400 (=> 16減少) str x3, sp, #-16! // sp = 0x000000016fdff3f0 (=> 16減少) ldr x0, sp, #16 // sp = 0x000000016fdff400 (=> 16増加) ldr x1, sp, #16 // sp = 0x000000016fdff410 (=> 16増加) ldr x2, sp, #16 // sp = 0x000000016fdff420 (=> 16増加) ldr x3, sp, #16 // sp = 0x000000016fdff430 (=> 16増加) スタックの実験で利用したコード
code:hello.s
.global _main
.align 2
_main:
// レジスタに値をセット
mov x0, 0x00
mov x1, 0x10
mov x2, 0x20
mov x3, 0x30
mov x4, 0x40
mov x5, 0x50
mov x6, 0x60
mov x7, 0x70
// スタックへのPUSH
// スタックからのPOP
ldr x0, sp, #16 // pop {x3} ldr x1, sp, #16 // pop {x2} ldr x2, sp, #16 // pop {x1} ldr x3, sp, #16 // pop {x0} // スタックへのPUSH (Part2)
// スタックからのPOP (Part2)
ldp x7, x6, sp // pop {x7, x6} ldp x5, x4, sp, #16 // pop {x7, x6} // print Hello World!
adr x1, helloworld // string to print
mov X2, #13 // length of our string mov X16, #4 // Unix write system call svc #0x80 // Call kernel to output the string // exit
mov x0, #0 // Use 0 return code mov x16, #1 // System call number 1 terminates this program svc #0x80 // Call kernel to terminate the program helloworld: .ascii "Hello World!\n"
SPをベースレジスタとして利用した場合の16バイトアライメント制限
SPをベースレジスタとしてメモリアクセスする場合、16バイトのアライメントに従う必要がある。
code:asm
// スタックへのPUSH
str x0, sp, #-8! // push {x0} // ベースのSPから16バイトを読み込むため、ここではアライメントエラーにならない str x1, sp, #-8! // push {x1} // 8バイト名から16バイトを読もうとするため、アライメントエラーが発生する SP以外のレジスタをベースレジスタとして利用する場合は、8バイトのアライメントでも大丈夫
code:asm
// x8 をベースレジスタとして利用
mov x8, sp
// スタックへのPUSH
// スタックからのPOP
ldr x0, x8, #8 // pop {x3} ldr x1, x8, #8 // pop {x2} ldr x2, x8, #8 // pop {x1} ldr x3, x8, #8 // pop {x0} リンクレジスタ(lr)とフレームポインタ(fp)の退避のしかた
stpとldpを使ってふたつのレジスタを同時にpush, popできる。lrとfpは常にペアで退避させるので、stpとldpを使うと良さそう
code:asm
// (おまけ)fpとlrのPUSHとPOP
stp fp, lr, sp, -16!; // push {fp, lr} ldp fp, lr, sp, 16 // pop {fp, lr} スタックへのPUSHとPOP(別解)
以下のようなスタックの操作の仕方もできる(こっちの書き方の方がオーソドックスな気がする)
code:asm
// スタックへPUSH
sub sp, sp, #32 // スタック領域を確保 stp x4, x5, sp // PUSH {x4, x5} stp x6, x7, sp, #16 // PUSH {x6, x7} // スタックからPOP
ldp x7, x6, sp // POP {x7, x6} ldp x5, x4, sp, #16 // POP {x7, x6} add sp, sp, #32 // スタック領域を解放